using System;
using System.Collections.Generic;
using System.Linq;

namespace CurrencyConv.Core
{
	public class CurrencyCalculator
	{
		private readonly IList<ExchangeRate> _exchangeRates;

		public CurrencyCalculator(IList<ExchangeRate> exchangeRates)
		{
			_exchangeRates = exchangeRates;
		}

		public Money ConvertWithRate(Money from, Money to, decimal exchangeRate)
		{
			decimal resultAmmount = from.Ammount / exchangeRate;
			return new Money(to, resultAmmount);
		}

		public Money Convert(Money from, Money to)
		{
			if (from.CurrencyCode == to.CurrencyCode)
				return from;

			ExchangeRate exchangeRate = _exchangeRates.FirstOrDefault(er => er.From.CurrencyCode == from.CurrencyCode && er.To.CurrencyCode == to.CurrencyCode);
			if (exchangeRate == null)
			{
				Money result = TryInverseConversion(from, to);

				return result ?? TryIndirectConversion(from, to);
			}

			return DoDirectConversion(from, to, exchangeRate);
		}

		private Money DoDirectConversion(Money from, Money to, ExchangeRate exchangeRate)
		{
			decimal resultAmmount = from.Ammount * exchangeRate.Rate;
			return new Money(to, resultAmmount);
		}


		private Money TryIndirectConversion(Money @from, Money to)
		{
			IEnumerable<IGrouping<Money, ExchangeRate>> groupedRates = (from exchangeRate in _exchangeRates
																																	where exchangeRate.To.CurrencyCode == @from.CurrencyCode || exchangeRate.To.CurrencyCode == to.CurrencyCode
																																	group exchangeRate by exchangeRate.From);
			ExchangeRate firstRate = null;
			ExchangeRate seccondRate = null;

			foreach (IGrouping<Money, ExchangeRate> groupedRate in groupedRates)
			{
				firstRate = groupedRate.FirstOrDefault(gr => gr.To.CurrencyCode == @from.CurrencyCode);
				seccondRate = groupedRate.FirstOrDefault(gr => gr.To.CurrencyCode == to.CurrencyCode);

				if (firstRate != null && seccondRate != null) break;

				firstRate = null;
				seccondRate = null;
			}

			if (firstRate == null) return null;

			Money baseCurrency = DoInverseConversion(@from, firstRate.From, firstRate);
			return DoDirectConversion(baseCurrency, to, seccondRate);
		}


		private Money TryInverseConversion(Money from, Money to)
		{
			ExchangeRate exchangeRate = _exchangeRates.FirstOrDefault(ex => ex.To.CurrencyCode == from.CurrencyCode);
			if (exchangeRate == null)
				return null;

			if (exchangeRate.From.CurrencyCode == to.CurrencyCode)
			{
				return DoInverseConversion(from, to, exchangeRate);
			}
			return null;
		}

		private Money DoInverseConversion(Money from, Money to, ExchangeRate exchangeRate)
		{
			decimal resultAmmount = from.Ammount / exchangeRate.Rate;
			return new Money(to, resultAmmount);
		}
	}
}